/*
 * Copyright 2001-2003 Neil Rotstan Copyright (C) 2004 Derek James and Philip Tucker
 * 
 * This file is part of JGAP.
 * 
 * JGAP is free software; you can redistribute it and/or modify it under the terms of the GNU
 * Lesser Public License as published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 * 
 * JGAP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser Public License along with JGAP; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307 USA
 * 
 * Modified on Feb 3, 2003 by Philip Tucker
 */
package org.jgap;

import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Random;
import java.util.Set;

/**
 * Abstract class for mutation operators. Handles iteration over population and updating
 * chromosome material with changes generated by subclass implementation of
 * <code>mutate( final Configuration config, 
 * final ChromosomeMaterial material, Set genesToAdd, Set genesToRemove )</code>.
 * @author Philip Tucker
 */
public abstract class MutationOperator {

private float mutationRate = 0.0f;

/**
 * @param aMutationRate
 */
public MutationOperator( float aMutationRate ) {
	mutationRate = aMutationRate;
}

/**
 * Leaves <code>material</code> unmodified, but updates <code>allelesToAdd</code> and
 * <code>allelesToRemove</code> with modifications. This interface was chosen at a time when
 * we wanted to have the mutation operators not augment each other; i.e., each one operated on
 * the original <code>material</code>, and the total <code>allelesToAdd</code> and
 * <code>allelesToRemove</code> from all mutations would be applied at once. We have gone back
 * to updating <code>material</code> after each mutation operator, but left the interface this
 * way in case we decide to switch again.
 * @param config
 * @param target chromosome material before mutation
 * @param allelesToAdd alleles added by this mutation, <code>Set</code> contains
 * <code>Allele</code> objects
 * @param allelesToRemove alleles removed by this mutation, <code>Set</code> contains
 * <code>Allele</code> objects
 * @throws InvalidConfigurationException
 */
protected abstract void mutate( final Configuration config, final ChromosomeMaterial target,
		Set allelesToAdd, Set allelesToRemove ) throws InvalidConfigurationException;

/**
 * The operate method will be invoked on each of the mutation operators referenced by the
 * current Configuration object during the evolution phase. Operators are given an opportunity
 * to run in the order that they are added to the Configuration.
 * @param config The current active genetic configuration.
 * @param offspring <code>List</code> Contains <code>ChromosomeMaterial</code> objects from
 * the current evolution. Material in this <code>List</code> should be modified directly.
 * @throws InvalidConfigurationException
 */
public void mutate( final Configuration config, final List offspring )
		throws InvalidConfigurationException {
	ListIterator iter = offspring.listIterator();
	while ( iter.hasNext() ) {
		ChromosomeMaterial material = (ChromosomeMaterial) iter.next();
		Set allelesToAdd = new HashSet();
		Set allelesToRemove = new HashSet();
		mutate( config, material, allelesToAdd, allelesToRemove );
		updateMaterial( material, allelesToAdd, allelesToRemove );
	}
}

/**
 * @return int number of mutations given <code>rand</code> random number generator,
 * <code>numOpportunities</code> number of oppurtunities for the mutation to occur, and the
 * configured mutation rate
 * @param rand
 * @param numOpportunities
 */
protected int numMutations( Random rand, int numOpportunities ) {
	// TODO - use some kind of stats lib to calculate this in a more clever way?
	int result = 0;
	if ( getMutationRate() > 0 ) {
		for ( int i = 0; i < numOpportunities; ++i ) {
			if ( doesMutationOccur( rand ) )
				++result;
		}
	}
	return result;
}

/**
 * @param rand
 * @return <code>true</code> when mutation rate and random chance dictate a mutation should
 * occur
 */
protected boolean doesMutationOccur( Random rand ) {
	return doesMutationOccur( rand, getMutationRate() );
}

/**
 * @param rand
 * @param mutationRate
 * @return <code>true</code> when mutation rate and random chance dictate a mutation should
 * occur
 */
protected static boolean doesMutationOccur( Random rand, float mutationRate ) {
	return ( rand.nextDouble() < mutationRate );
}

/**
 * @return mutation rate
 */
public float getMutationRate() {
	return mutationRate;
}

/**
 * updates <code>material</code> with specified sets of alleles; alleles present in both lists
 * will be added (or replaced if the gene existed on original material)
 * @param material
 * @param allelesToAdd <code>Set</code> contains <code>Allele</code> objecs
 * @param allelesToRemove <code>Set</code> contains <code>Allele</code> objects
 */
protected static void updateMaterial( ChromosomeMaterial material, Set allelesToAdd,
		Set allelesToRemove ) {
	// remove before add because some genes that have been modified are in both lists
	material.getAlleles().removeAll( allelesToRemove );
	material.getAlleles().addAll( allelesToAdd );
}

/**
 * @param aMutationRate
 */
protected void setMutationRate( float aMutationRate ) {
	mutationRate = aMutationRate;
}

}
